---
description: 'Build a Serverless AI-Powered Python API'
tags:
  - API
  - AI & Machine Learning
languages:
  - python
image: /docs/images/guides/serverless-ai-api/banner.png
image_alt: 'Serverless AI-Powered API Banner'
featured:
  image: /docs/images/guides/serverless-ai-api/featured.png
  image_alt: 'Serverless AI-Powered API Banner'
published_at: 2025-05-09
---

# Build a Serverless AI-Powered Python API

**TL;DR:** _In this tutorial, you'll learn how to build and deploy a **serverless** backend in Python that integrates an **AI** text summarization feature - all using the open-source Nitric framework. We'll go from setting up a Nitric project, writing an API endpoint that calls an AI service, testing it locally (with a slick built-in dashboard), to deploying it on the cloud. No prior cloud knowledge or tedious infrastructure configuration required._

Modern backend development is exciting and fast-moving - **serverless architectures** and **AI integrations** are among 2025's hottest trends. Yet, if you're a Python developer with minimal cloud experience, diving into these topics can feel daunting. This guide is here to help. We'll leverage **Nitric**, a cloud-agnostic framework that abstracts away the cloud complexity, letting you focus on code, not servers or YAML config. By the end, you'll have a working **Python API** that takes in text and returns an AI-generated summary, deployed on a cloud provider.

## Why Nitric? (and What We're Building)

**Nitric** is an open-source framework that acts as a bridge between your code and cloud services, taking care of provisioning resources (APIs, queues, databases, etc.) and wiring up permissions behind the scenes. It's multi-language and multi-cloud, meaning you can write in Python (or TypeScript, Go, etc.) and deploy to AWS, Azure, or GCP without changing your code. This saves tons of boilerplate and avoids cloud vendor lock-in.

**What we'll build:** a RESTful API (using Nitric) with a single endpoint that accepts a block of text and returns a concise summary generated by an AI. We'll use OpenAI's API for the text summarization and by the end, you can deploy it and hit a live endpoint that summarizes text for you. Along the way, you'll see how Nitric simplifies common tasks (like local testing, resource provisioning, and deployment).

**Key Nitric benefits we'll see in action:**

- _Developer-friendly:_ Feels familiar if you've used web frameworks (you'll define routes and handlers in Python). No need to write Terraform or juggle AWS consoles - Nitric infers the necessary cloud resources from your code.
- _Accelerated development:_ Built-in local **development server** and dashboard for instant testing and debugging. You can run your serverless app locally with one command and even see a visual API explorer.
- _Portability:_ Nitric lets you **deploy anywhere** - AWS, GCP, Azure - by simply switching a setting, with all cloud-specific details handled for you. We'll deploy to AWS in this tutorial, but you could choose another cloud with minimal changes.
- _Minimal configuration:_ The framework uses an “infrastructure from code” approach. We won't write separate infrastructure files; our Python code (and a tiny project config) is enough for Nitric to provision what's needed.

Now, let's get our tools ready and dive in.

## Prerequisites

Before we begin, make sure you have the following:

- **Python 3.11+** installed on your system (the latest 3.x is fine). Basic familiarity with Python is assumed.
- **Nitric CLI** installed - this is the command-line tool to create, run, and deploy Nitric projects. You can install it by following the [installation instructions](/get-started/installation).
- **uv** or **Pipenv** for managing Python dependencies. The Nitric Python templates use either uv or Pipenv.
- An **OpenAI API key** if you want to integrate with OpenAI's GPT for summarization.
- An **AWS account** (or Google Cloud or Azure account) with credentials configured on your machine, if you plan to deploy to the cloud. Nitric will use these credentials to provision resources. (For AWS, having the AWS CLI configured or environment vars set for access keys is enough.)

<Note>
  You can deploy to any of the three major clouds - Nitric supports all. In this
  tutorial we'll mention AWS for concreteness, but feel free to use your
  preferred cloud.
</Note>

## Project Setup

Nitric projects have a simple structure: your code (services, functions, etc.) lives in a directory (by default called `services/`), and a `nitric.yaml` config file describes high-level settings (like which files are services and what runtime to use). We'll use the Nitric CLI to scaffold a new project:

1. **Create a new Nitric project:** Open a terminal and run:

```bash
nitric new my-summarizer py-starter
```

This will create a new folder `my-summarizer` with a Python starter template. (You can choose a different name for your project; `py-starter` selects the Python starter template.) If you omit the template, the CLI will interactively ask for a language/template. The CLI's `new` command guides you through project creation and offers ready-made templates.

If none of the templates suit your needs, you can always create your own.

2. **Install dependencies:** Navigate into the project and install the requirements. The template uses uv, so you can do:

```bash
cd my-summarizer
uv sync
```

This will install Nitric's Python SDK (and any other dependencies). If you used a different template, change this step to install dependencies accordingly.

3. **Explore the scaffold:** The generated project should have a structure like:

```
my-summarizer/
├── services/
│   └── api.py
├── nitric.yaml
├── pyproject.toml
├── python.dockerfile
├── python.dockerfile.dockerignore
└── README.md
```

The `services/api.py` is a sample service that Nitric created (a simple “Hello, World” HTTP endpoint). We'll replace this with our own code. The `nitric.yaml` config by default tells Nitric to treat any `*.py` in `services/` as a service and what runtime to use. We'll keep the config mostly default.

4. **(Optional) Verify the template works:** You can test the starter project right away by running `nitric start` inside the project. This spins up Nitric's local development server. Check the terminal output - you should see that an API endpoint is available locally at some port (like 4001). For now, stop the server (Ctrl+C) - we're going to write our own service next.

With the project set up, let's start coding our API.

## Writing the Python API Service (with AI Integration)

Our goal is to create an HTTP endpoint `/summarize` (for example) that accepts some text and returns a summary of that text. We'll implement this as an **API service** in Nitric. In Nitric's model, an **API** can have multiple RESTful routes, served by one or more services. We define an API and then use decorators to attach function handlers to specific HTTP methods and paths - very similar to frameworks like Flask or FastAPI, but with Nitric's `@api` decorators.

**Steps:**

1. **Create a new service file:** In the `services/` directory, create a file `summarize.py` (you can name it differently, but let's go with this for clarity).

2. **Import Nitric and dependencies:** We'll need Nitric's API utilities and the OpenAI library (for AI). Make sure you add `openai` to your dependencies (`uv add openai`). Then, in `summarize.py`:

```python title:services/summarize.py
import os
from openai import OpenAI
from nitric.resources import api
from nitric.application import Nitric
from nitric.context import HttpContext
```

Here we import `api` to create an API resource, `Nitric` to run the app, and `HttpContext` for typing (the context object passed into handlers). We'll use `os` to read the OpenAI API key from an environment variable, and `openai` to call the OpenAI service.

3. **Configure OpenAI API key:** It's best not to hardcode secrets. If you have your OpenAI API key, set it as an environment variable (e.g., create a .env file for the project containing `OPENAI_API_KEY=sk-...`). We can then configure the OpenAI client in code:

```python title:services/summarize.py
# Configure OpenAI API key from env
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
  raise Exception("Please set the OPENAI_API_KEY environment variable")
```

This will fetch the API key from your environment. (If you prefer, Nitric also has a built-in **Secrets** feature to securely manage secrets, but for simplicity we'll stick to an env var here.)

4. **Create the OpenAI client:** Initialise the `OpenAI` client using the fetched API key.

```python title:services/summarize.py
# Create OpenAI client
# This could be a local model during development if you have Ollama or similar installed.
# For example, if you have Ollama running locally, you could use:
# client = OpenAI(model="ollama", , base_url="http://localhost:11434/v1")
client = OpenAI(api_key=api_key)
```

5. **Define the API and route handler:** Now, use Nitric to create a POST route for summarization:

```python title:services/summarize.py
# Create a Nitric API named 'main'
summarize_api = api("main")

@summarize_api.post("/summarize")
async def summarize_text(ctx: HttpContext) -> None:
  # Extract input text from request (assuming JSON body with a 'text' field)
  req_data = ctx.req.json
  if not req_data or 'text' not in req_data:
    ctx.res.status = 400
    ctx.res.body = {"error": "No text provided for summarization."}
    return

  text_to_summarize = req_data['text']
  # Call OpenAI API to get summary (this is a blocking call, for demo simplicity)
  try:
    response = client.chat.completions.create(
      # This model could be replaced with another of your choice.
      # Using tools like Ollama, it could even be a local model.
      model="gpt-3.5-turbo",
      messages=[{"role": "user", "content": f"Summarize the following text:\n\n{text_to_summarize}"}],
      max_tokens=50
    )
    # Extract the assistant's reply (summary text)
    summary = response.choices[0].message.content
    # Set response
    ctx.res.body = {"summary": summary.strip()}
  except Exception as e:
    ctx.res.status = 500
    ctx.res.body = {"error": str(e)}

```

Let's break down what's happening:

- We created an API service named `"main"`. (Nitric uses this name to group routes; you can have multiple APIs. The default template also uses `"main"` for the primary API, which is why we chose it here.)
- We define a POST route at path `/summarize` using a decorator `@summarize_api.post("/summarize")`. This means our function `summarize_text` will handle HTTP POST requests to `/summarize`.
- In the handler, we access `ctx.req.json` - Nitric conveniently parses JSON request bodies for us into `ctx.req.json`. We expect the client to send JSON like `{"text": "some long text to summarize"}`. If the JSON or field is missing, we return a 400 Bad Request with an error message.
- We then call the OpenAI API (using the ChatGPT model `gpt-3.5-turbo`) with a prompt asking to summarize the input text. We limit the `max_tokens` for the summary to ~100 tokens (roughly a few sentences) to keep it concise.
- The OpenAI response contains a list of choices; we extract the content of the first choice (which is the model's summary text).
- We set `ctx.res.body` to a JSON object containing the summary. Nitric will serialize this to JSON for the HTTP response. By default, returning a Python dict as `res.body` results in a JSON response.
- If the OpenAI API call fails (e.g., network issue or invalid key), we catch the exception and return a 500 error with the message.

_A note on async:_ We defined the handler as `async def`. The Nitric framework supports async handlers, which is great for I/O operations. The OpenAI Python SDK call we used (`ChatCompletion.create`) is a blocking call. In a real app, you might integrate an async HTTP client or use OpenAI's async methods (if available) to avoid blocking the event loop. However, for simplicity in this tutorial, this is acceptable - our focus is on the integration, not optimizing async usage.

5. **Finalize the Nitric app run:** After defining all your routes and handlers in the file, make sure to add:

```python title:services/summarize.py
Nitric.run()
```

This call tells Nitric to start the application (it will scan for all defined services/routes and run the local server when invoked via `nitric start`). In our case, it picks up `summarize_api` and the attached route.

That's it for our service code! We have a complete Python backend that will accept text and return an AI-generated summary. The entire `summarize.py` file should look like this (for reference):

```python title:services/summarize.py
import os
from openai import OpenAI
from nitric.resources import api
from nitric.application import Nitric
from nitric.context import HttpContext

# Configure OpenAI API key from env
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
  raise Exception("Please set the OPENAI_API_KEY environment variable")

# Create OpenAI client
client = OpenAI(api_key=api_key)

# Create a Nitric API named 'main'
summarize_api = api("main")

@summarize_api.post("/summarize")
async def summarize_text(ctx: HttpContext) -> None:
  # Extract input text from request (assuming JSON body with a 'text' field)
  req_data = ctx.req.json
  if not req_data or 'text' not in req_data:
    ctx.res.status = 400
    ctx.res.body = {"error": "No text provided for summarization."}
    return

  text_to_summarize = req_data['text']
  # Call OpenAI API to get summary (this is a blocking call, for demo simplicity)
  try:
    response = client.chat.completions.create(
      model="gpt-3.5-turbo",
      messages=[{"role": "user", "content": f"Summarize the following text:\n\n{text_to_summarize}"}],
      max_tokens=50
    )
    # Extract the assistant's reply (summary text)
    summary = response.choices[0].message.content
    # Set response
    ctx.res.body = {"summary": summary.strip()}
  except Exception as e:
    ctx.res.status = 500
    ctx.res.body = {"error": str(e)}

Nitric.run()
```

With our code ready, let's test it out locally before deploying.

## Testing Locally with Nitric

One of Nitric's superpowers is its local development experience. You can run your cloud app locally, _including_ any cloud resources it defines, using the CLI. Nitric will simulate the cloud services and even provide a local web **dashboard** where you can interact with your API (no need for Postman or separate testing tools).

Make sure you've saved your `summarize.py`. Also ensure the old `api.py` (from the template) is either removed or its content is replaced - we only want one service running (our new one). Now, run:

```bash
nitric start
```

This command will compile and start your Nitric application locally. By default, Nitric will assign a local port for your API (usually 4001 for the first API service). In the console output, look for a line that indicates the API endpoint and port (e.g., "api:main - http://localhost:4001").

Once the local server is up, two ways to test:

**A. Using the Nitric Local Dashboard:** The CLI should automatically open a browser window for the Nitric dashboard (or you can navigate to the URL it shows, typically `localhost:49152` for the dashboard UI). On the left, you'll see your **APIs** and their routes. You should see an API (named “main”) with a POST endpoint `/summarize`. You can click that and use the interface to provide input and send a request.

After sending a request with some text, you should see the response at the bottom of the dashboard. If all went well, it will contain a JSON with a summary. (For example, summarizing a paragraph about Nitric might return something like `{"summary": "Nitric is a framework that simplifies cloud app development by handling infrastructure from code."}`)

You can also check the console logs where `nitric start` is running to see any log output or errors. If the OpenAI call fails (e.g., because of a missing/invalid API key), you'll see the error stack trace there.

**B. Using cURL or another HTTP client:** Alternatively, you can test without the dashboard. Since the app is running on localhost (say port 4001), you can use a command-line HTTP request. For example:

```bash
curl -X POST http://localhost:4001/summarize \
  -H "Content-Type: application/json" \
  -d '{"text": "Nitric is an open-source framework that handles cloud infrastructure from code, making it easier to build and deploy applications."}'
```

This should return a JSON response with a summary. You might see output like:

```json
{
  "summary": "Nitric is an open-source framework that simplifies building and deploying cloud applications by handling the cloud infrastructure details for developers."
}
```

Feel free to experiment with different input texts. Because we're using a general AI model, it should be able to summarize most coherent English text. Keep the input relatively short in these tests to avoid hitting token limits.

Local testing is super fast - you can edit your Python code, save, and Nitric auto-reloads the changes (the dashboard and local server update on the fly). This tight feedback loop means you can iterate on your function logic quickly. Once you're satisfied with local results, it's time to deploy our app to the cloud 🚀.

## Deploying to the Cloud

Deploying a serverless app usually involves setting up cloud services (like an API gateway, a function, IAM permissions, etc.) which can be a lot of work manually. Nitric automates **all of this** for us. With a couple of CLI commands, Nitric will provision the necessary resources on your chosen cloud provider and get our API running live.

**1. Create a deployable stack:** In Nitric, a _stack_ is basically a deployment environment (combining your app code with cloud config like region and provider). We need to create one before our first deploy. Run:

```bash
nitric stack new prod-stack
```

_(You can name the stack as you like, e.g., “dev-stack” or “prod-stack”.)_

The CLI will prompt you a cloud provider (choose “AWS” for this tutorial, or GCP/Azure if you prefer),

This command creates a stack configuration file (for example, `prod-stack.yaml`) in your project. Open this file - you'll see it lists the chosen provider, an empty region key, and some default config for how to deploy your services. Nitric has already made best-practice choices (like naming conventions, runtime settings, etc.) which you can tweak if needed. Ensure the region is set correctly for your account.

**2. Deploy the stack:** Now the magic moment. Run:

```bash
nitric up
```

This tells Nitric to deploy (“up”) the stack we just created. The CLI will now translate your application into cloud resources and deploy them. Under the hood, Nitric uses cloud IaC providers (like Pulumi or Terraform) to create the infra from your code definitions, but you don't have to write any of that yourself. It will automatically set up everything: an HTTP endpoint (API Gateway or equivalent), a cloud function to run your `summarize_text` code, and any required permissions (IAM roles, etc.).

You'll see a progress log in the console. The first deployment may take a few minutes as it's creating cloud resources. Once complete, Nitric will output the **endpoint URL** for your API. It might look something like:

```
https://xxxxxx.execute-api.us-east-1.amazonaws.com
```

(The exact format depends on the cloud - for AWS, it uses API Gateway; for others, a similar endpoint will be provided.

**3. Test the live endpoint:** Time to test our deployed function on the cloud! You can use `curl` again, but now replace the localhost URL with the one given by Nitric. For example:

```bash
curl -X POST https://<<your-api-url>>/summarize \
  -H "Content-Type: application/json" \
  -d '{"text": "OpenAI API integration with serverless functions is very powerful, allowing applications to leverage AI without managing infrastructure."}'
```

_Make sure to put your actual endpoint URL in place of `<<your-api-url>>`._

You should get a JSON response with a summary, similar to what you saw locally. Congratulations - your AI-powered summarization service is now live on the internet, running completely on serverless infrastructure! 🎉

A few things to appreciate here:

- We **did not manually configure** any AWS Lambda, API Gateway, IAM role, etc. Nitric handled all that configuration for us based on our code. When we ran `nitric up`, it knew we defined an API with a POST route, so it provisioned the necessary API endpoint and a function to handle it, plus the permissions (for example, allowing the function to make outbound network calls, etc.). In traditional setups, you might have written Terraform or clicked around the console for each of those pieces. Nitric delivered it in one shot.
- If you wanted to deploy the same app to another cloud, you could create another stack (say `nitric stack new gcp-stack` and select GCP) and run `nitric up -s gcp-stack`. Your code wouldn't need any changes - **multi-cloud deployment without code changes** is a core benefit of Nitric.
- The endpoint is secure and ready to use. You could integrate it with a frontend application (imagine a simple web form that calls this API and displays the summary) or share it with others to use (just be mindful of your OpenAI API usage, since that can incur cost).

**Important:** Remember that our cloud function still needs the OpenAI API key. How did that work on deploy? If you deployed to AWS, Nitric likely packaged your local environment variable into the cloud configuration. Nitric will include any values in a .env file present at deploy time, by default, but there are many other options for dealing with secrets and config.

## Wrap Up and Next Steps

In this tutorial, we built a **serverless** Python API that integrates an **AI** service, and we did it without wrestling with cloud infrastructure. Here's a quick recap of what we achieved:

- **Used Nitric to scaffold a project** and avoid boilerplate. We wrote just one Python file for our logic; Nitric inferred the rest.
- **Implemented an API endpoint** with Python async syntax, leveraging Nitric's simple decorators to define routes.
- **Integrated an AI API (OpenAI)** to perform text summarization - demonstrating how easy it is to plug in powerful third-party services in a serverless function.
- **Tested locally** with Nitric's built-in dashboard and CLI (`nitric start`), getting instant feedback without deploying to the cloud for each change.
- **Deployed to the cloud** with a couple of commands. Nitric provisioned the necessary cloud resources and gave us a live endpoint, all without us writing any infrastructure code. The framework “understood” our application's needs and handled the heavy lifting.

All of this was done without needing to be AWS experts or know how to set up API Gateway, etc. Nitric's philosophy is to let developers focus on app logic while it ensures the cloud plumbing is correct and optimal. This means faster development cycles and less chance of misconfigurations. For example, Nitric set up IAM roles with only the permissions our app needed, reducing security risks by default.

**Where to go from here?** The possibilities are vast:

- You can extend this project by adding more endpoints. For instance, you could add a `/sentiment` endpoint that uses a different AI model or library to do sentiment analysis on text. Simply define another route with `@summarize_api.post("/sentiment")` in the same file or a new service file - Nitric will incorporate it and you can deploy the update.
- Add other Nitric resources: need a [database](/sql) or [key-value store](/keyvalue) to cache results? Nitric offers `sql()` and `kv()` resources that can be used similarly to how we defined the API. Want to trigger actions on a schedule (cron jobs) or handle file storage in the cloud? Nitric has primitives for [schedules](/schedules), [buckets](/storage) (object storage), [queues](/messaging#queues), pub-sub [topics](/messaging#topics), and more - all accessible through simple code constructs, and all cloud-agnostic. (For example, you could schedule a daily job to fetch some data and summarize it, using Nitric's `schedule` resource.)
- Deploy to another cloud: Try deploying the same app to Azure or GCP. It should be as easy as creating a new stack for that provider. This can be a great way to learn differences in cloud offerings without changing your code - Nitric will map your API to the equivalent service (e.g., Azure Container Apps + API Management, or GCP Cloud Run + API Gateway).

As you continue exploring, check out the official Nitric documentation and examples for deeper dives. The [Nitric docs site](https://nitric.io/docs) has guides on all resource types and features (for instance, how to use **Queues/Topics for event-driven patterns**, or how to secure your API with auth). There's also a community [Discord](https://nitric.io/chat) and more [examples](https://github.com/nitrictech/examples) on GitHub if you want inspiration.

We hope this tutorial showed you that modern cloud development doesn't have to be intimidating. Using Nitric, you offloaded the undifferentiated heavy lifting of cloud setup and got straight to building something useful. Happy coding, and may all your deployments be this smooth! 🚀
